<?php
// +----------------------------------------------------------------------
// | OneThink [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2013 http://www.onethink.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: huajie <banhuajie@163.com>
// +----------------------------------------------------------------------

namespace Admin\Controller;
use OT\File;

/**
 * 在线更新
 * @author huajie <banhuajie@163.com>
 */
class UpdateController extends AdminController{

	/**
	 * 初始化页面
	 * @author huajie <banhuajie@163.com>
	 */
	public function index(){
		$this->meta_title = '在线更新';
		if(IS_POST){
			$this->display();

            //TCHAT 检查本地有无更新包：
            $pagPath =  './Public/Updates/update.zip';
            if(is_file($pagPath)){
                //PclZip类库不支持命名空间
                import('OT/PclZip');
                $zipPath = $pagPath;
                $this->showMsg('检测到本地更新包', 'success');
                $this->unzipFile($zipPath);
                unlink($pagPath);
                exit;
            }

            //检查新版本
            $lastVersion = $this->checkVersion();

			//在线更新
			$this->update($lastVersion['version']);
		}else{
			$this->display();
		}
	}

	/**
	 * 检查新版本
	 * @author huajie <banhuajie@163.com>
	 */
	private function checkVersion(){
		if(extension_loaded('curl')){
			$url = 'http://www.juexin.pro/Home/CheckVersion';
			$params = array(
					'version' => ONECHAT_VERSION,
					'domain'  => $_SERVER['HTTP_HOST'],
					'auth'    => sha1(C('DATA_AUTH_KEY')),
			);
			$vars = http_build_query($params);
			//获取版本数据
			$data = $this->getRemoteUrl($url, 'post', $vars);
			if(!empty($data)){
				$this->showMsg('发现新版本：'.$data['version'], 'success');
				return $data;
			}else{
				$this->showMsg("未发现新版本", 'error');
				exit;
			}
		}else{
			$this->error('请配置支持curl');
		}
	}

	/**
	 * 在线更新
     * TCHAT 在线更新做了调整
	 * @author huajie <banhuajie@163.com>
	 */
	private function update($version){

		//PclZip类库不支持命名空间
		import('OT/PclZip');

		$date  = date('YmdHis');
		$backupFile = I('post.backupfile');
        $updateDatabase = I('post.updatedatabase');
		sleep(1);

		$this->showMsg('OneChat系统原始版本:'.ONECHAT_VERSION);
		$this->showMsg('OneChat在线更新日志：');
		$this->showMsg('更新开始时间:'.date('Y-m-d H:i:s'));
		sleep(1);

        if($updateDatabase){//是否只更新数据库
            /* 更新数据库 */
            $this->updateDataBase();

        }else{
            /* 建立更新文件夹 */
            $folder = $this->getUpdateFolder();
            File::mk_dir($folder);
            $folder = $folder.'/'.$date;
            File::mk_dir($folder);

            //备份重要文件
            if($backupFile){
                $this->showMsg('开始备份重要程序文件...');
                G('start1');
                $backupallPath = $folder.'/backupall.zip';
                $zip = new \PclZip($backupallPath);
                $zip->create('Application,ThinkPHP,.htaccess,admin.php,index.php,wechat.php,web.config');
                $this->showMsg('成功完成重要程序备份,备份文件路径:<a href=\''.__ROOT__.$backupallPath.'\'>'.$backupallPath.'</a>, 耗时:'.G('start1','stop1').'s','success');
            }

            $this->showMsg('开始检测远程更新包下载地址', 'success');
            //获取在线更新包地址
            $updatedUrl = 'http://www.juexin.pro/Home/CheckVersion/getDownloadUrl';
            $params = array('version' => ONECHAT_VERSION);
            $urls = $this->getRemoteUrl($updatedUrl, 'post', http_build_query($params));

            if(empty($urls)){
                $this->showMsg('未获取到更新包的下载地址', 'error');
                exit;
            }else{;
                $this->showMsg('获取到更新包的下载地址', 'success');
            }

            foreach($urls as $v){
                //下载并保存
                $this->showMsg('开始获取版本'.$v['version'].'更新包...');
                sleep(1);
                $zipPath = $folder.'/update'.$v['version'].'.zip';
                $downZip = file_get_contents($v['url']);
                if(empty($downZip)){
                    $this->showMsg('下载更新包出错，请重试！', 'error');
                    $this->showMsg('更新包地址：'.$v['url'], 'error');
                    exit;
                }
                File::write_file($zipPath, $downZip);
                $this->showMsg('获取远程更新包成功,更新包路径：<a href=\''.__ROOT__.ltrim($zipPath,'.').'\'>'.$zipPath.'</a>', 'success');
                sleep(1);
                $this->unzipFile($zipPath,$version);
            }

        }

		$this->showMsg('##################################################################');
		$this->showMsg('在线更新全部完成，如有备份，请及时将备份文件移动至非web目录下！', 'success');
	}

    private function unzipFile($zipPath,$version = ''){
        /* 解压缩更新包 */ //TODO: 检查权限
        $this->showMsg('版本'.$version.'更新包解压缩...');
        sleep(1);
        $zip = new \PclZip($zipPath);
        $res = $zip->extract(PCLZIP_OPT_PATH,'./');
        if($res === 0){
            $this->showMsg('版本'.$version.'解压缩失败：'.$zip->errorInfo(true).'------更新终止', 'error');
            exit;
        }
        $this->showMsg('版本'.$version.'更新包解压缩成功', 'success');
        sleep(1);

        /* 更新数据库 */
        $this->updateDataBase();

        /* 系统版本号更新 */
        if(!empty($version)){
            $file = File::read_file(COMMON_PATH.'Common/function.php');
            $file = str_replace(ONECHAT_VERSION, $version, $file);
            $res = File::write_file(COMMON_PATH.'Common/function.php', $file);
            if($res === false){
                $this->showMsg('更新系统版本号'.$version.'失败', 'error');
            }else{
                $this->showMsg('更新系统版本号'.$version.'成功', 'success');
            }
        }

    }
    /**
     * 更新数据库
     * TCHAT 对数据库的更新做了调整
     */
    private function updateDataBase() {

        $updatesql = './update.sql';
        if(is_file($updatesql))
        {
            $this->showMsg('更新数据库开始...');
            if(file_exists($updatesql))
            {
                $sql = File::read_file($updatesql);
                $sql = str_replace("\r\n", "\n", $sql);
                $sql = explode(";\n" , $sql);//将字符串整理为数组

                /*替换表前缀*/
                $orginal = 'onethink_';
                $prefix = C('DB_PREFIX');//获取表前缀
                $sql = str_replace(" `{$orginal}}" , " `{$prefix}" , $sql);
                $Model = M(); //建立空模型

                /*遍历整理后的sql数组*/
                foreach ( $sql as $value ) {
                    //var_dump($value);
                    $value = trim($value);
                    if ( empty( $value ) ) {
                        continue;
                    } else {

                        //判断是不是对字段进行的操作
                        if(preg_match('/ALTER/',$value)){

                            //新增字段的判断操作
                            if(preg_match('/TABLE `(.+)` ADD `(.+)` /',$value,$match)){
                                //表名：$match[1] 字段名：$match[2] 判断该字段不存在就执行新增
                                $check = $Model->execute('Describe `'.$match[1].'` `'.$match[2].'`');
                                if($check == NULL){
                                    $Model->execute($value);
                                };

                            }else

                                //删除字段的判断操作
                                if(preg_match('/TABLE\s*`(.+)`\s*DROP\s*`(.+)`\s*/',$value,$match)){
                                    //表名：$match[1] 字段名：$match[2] 判断该字段存在就执行删除
                                    $check =$Model->execute('Describe `'.$match[1].'` `'.$match[2].'`');
                                    if($check){
                                        $Model->execute($value);
                                    };
                                }else

                                    //替换字段名称或定义的判断操作
                                    if(preg_match('/TABLE `(.+)`\s+CHANGE\s+`(.+)`\s+`(.+)`\s/',$value,$match)){
                                        //更新字段定义
                                        if($match[2] == $match[3]){
                                            $Model->execute($value);
                                            //更新字段名称及定义
                                        }else{
                                            //表名：$match[1] 原字段名：$match[2] 新字段名：$match[3]
                                            $checkOld = $Model->execute('Describe `'.$match[1].'` `'.$match[2].'`');
                                            $checkNew = $Model->execute('Describe `'.$match[1].'` `'.$match[3].'`');
                                            //如果新旧字段同时存在 就删除原来的新字段名
                                            if($checkOld && $checkNew){
                                                $Model->execute('ALTER TABLE `'.$match[1].'` DROP `'.$match[3].'`');
                                            }
                                            //如果新旧字段同时都不存在 将更新指令替换为新增指令
                                            if(!$checkOld && !$checkNew){
                                                preg_match('/TABLE `.+`\s+(CHANGE\s+`.+`)\s+`.+`\s/',$value,$match);
                                                $value = str_replace($match[1],'ADD',$value);
                                            }
                                            //如果旧字段不存在 将原存储的新字段更新
                                            if(!$checkOld && $checkNew){
                                                preg_match('/TABLE `.+`\s+CHANGE\s+(`.+`)\s+(`.+`)\s/',$value,$match);
                                                $value = str_replace($match[1],$match[2],$value);
                                            }

                                            $Model->execute($value);
                                        }
                                    }else{
                                        //调试有无ALTER语句没有执行使用，请在调试时打开，不调试时就不要
                                        //var_dump($value);
                                    }
                            continue;

                        }else{
                            $Model->execute($value);
                        }
                    }
                }
            }
            unlink($updatesql);
            $this->showMsg('更新数据库完毕', 'success');
        }else{
            $this->showMsg('数据库更新文件不存在或无需更新数据库', 'error');
        }
    }


    /**
	 * 获取远程数据
	 * @author huajie <banhuajie@163.com>
	 */
	private function getRemoteUrl($url = '', $method = '', $param = ''){
		$opts = array(
			CURLOPT_TIMEOUT        => 20,
			CURLOPT_RETURNTRANSFER => true,
			CURLOPT_URL            => $url,
			CURLOPT_USERAGENT      => $_SERVER['HTTP_USER_AGENT'],
		);
		if($method === 'post'){
			$opts[CURLOPT_POST] = 1;
			$opts[CURLOPT_POSTFIELDS] = $param;
		}

		/* 初始化并执行curl请求 */
		$ch = curl_init();
		curl_setopt_array($ch, $opts);
		$data  = curl_exec($ch);
		$error = curl_error($ch);
		curl_close($ch);
        $data = json_decode($data,true);
		return $data;
	}

	/**
	 * 实时显示提示信息
	 * @param  string $msg 提示信息
	 * @param  string $class 输出样式（success:成功，error:失败）
	 * @author huajie <banhuajie@163.com>
	 */
	private function showMsg($msg, $class = ''){
		echo "<script type=\"text/javascript\">showmsg(\"{$msg}\",\"{$class}\")</script>";
		flush();
		ob_flush();
	}

	/**
	 * 生成更新文件夹名
	 * @author huajie <banhuajie@163.com>
	 */
	private function getUpdateFolder(){
		$key = sha1(C('DATA_AUTH_KEY'));
		return 'update_'.$key;
	}
}